
/*
 * WekaArffTableModel.java
 *
 * Created on 2007-nov-10, 22:11:27
 *
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 *///TODO Init FoldColumn after all data is added;
package wekaGrexBridge;

import grex.Data.ArffTableModel;
import grex.Environment;
import grex.GP;
import grex.Options;
import grex.Prediction;
import grex.PredictionContainer;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;

import weka.classifiers.Classifier;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;

/**
 *
 * @author RIK
 */
public class WekaArffTableModel extends ArffTableModel {

    Instances instances;
    private ModelInjection modelInjection;

/*    public WekaArffTableModel(PredictionContainer pc) {

        ArrayList<Attribute> attInfo = new ArrayList<Attribute>();
        int nrOfAttributes = pc.values().iterator().next().getInstance().length;
        ArrayList<HashSet<String>> allowedValues = new ArrayList<HashSet<String>>(nrOfAttributes);
        for (int i = 0; i < nrOfAttributes; i++) {
            allowedValues.add(new HashSet<String>());
        }
        for (Prediction p : pc.values()) {
            double[] instance = p.getInstance();
            for (int i = 0; i < nrOfAttributes; i++) {
                allowedValues.get(i).add("" + instance[i]);
            }
        }
        HashSet set = allowedValues.get(0);
        ArrayList<String> a = new ArrayList<String>(set);

        for (int i = 0; i < nrOfAttributes; i++) { //Tricky with the real types, gene must have acces to arffTable!
            if (allowedValues.get(i).size() > 3) {//Treat as numeric if more than three values
                attInfo.add(new Attribute("Col:" + i, i));
            } else {
                attInfo.add(new Attribute("Col:" + i, new ArrayList<String>(allowedValues.get(i)), i));
            }
        }
        instances = new Instances("Dataset", attInfo, 0);
        instances.setClassIndex(nrOfAttributes - 1);
    }*/

    public WekaArffTableModel(ModelInjection mi, ArffTableModel atm) {
        this(atm);
        this.modelInjection = mi;
    }

    public WekaArffTableModel(ArffTableModel atm) {
        super(atm);

        ArrayList<Attribute> attInfo = new ArrayList<Attribute>();

        for (int i = 0; i < arlAllowedValues.size() - 1; i++) {//Dont add FodColumn
            if (isNumericType(arlAllowedValues.get(i))) {
                attInfo.add(new Attribute(atm.getColumnName(i), i));
            } else {
                attInfo.add(new Attribute(atm.getColumnName(i), getArlAllowedValues().get(i), i));
            }
        }
        instances = new Instances(this.getTableName(), attInfo, 0);
        instances.setClassIndex(targetColumnIndex);
    }
    
    public WekaArffTableModel(Environment env) { //this constructor is only used when the mdoel is used as an node in a GP. Hence ni need do initilalize the whole ArffTable class with a call to super()        
        ArrayList<Attribute> attInfo = new ArrayList<Attribute>();
        ArrayList<ArrayList<String>> allowedValues = env.getData().getArlAllowedValues();
        for (int i = 0; i < allowedValues.size() - 1; i++) {//Dont add FodColumn
            if (env.getData().isNumericType(allowedValues.get(i))) {
                attInfo.add(new Attribute(env.getData().getColumnName(i), i));
            } else {
                attInfo.add(new Attribute(env.getData().getColumnName(i), allowedValues.get(i), i));
            }
        }
        instances = new Instances(env.getData().getTableName(), attInfo, 0);
        instances.setClassIndex(env.getTargetColumnIndex());
    }
    
    //Alternativ l�sning
    public Instances getTrainInstances(String currentFold) {
        return getInstances(this.getTrainFold(currentFold));
    }

    public Instances getTestInstances(String currentFold) {
        return getInstances(this.getTestFold(currentFold));
    }
    
    public Instances getValInstances(String currentFold) {
        return getInstances(this.getValFold(currentFold));
    }
    
    
    public Instances getInstances(PredictionContainer pc) {

        Instances dataset = instances.stringFreeStructure();

        for (Prediction p:pc.values()) {
            //       if(data[i][0]==1){ //If promotion
            DenseInstance inst = new DenseInstance(1, p.getInstance());
            inst.setDataset(dataset);
            dataset.add(inst);
            //     }
        }//*/

        return dataset;
    }    
    

    private Instances getInstances(double[][] data) {

        Instances dataset = instances.stringFreeStructure();

        for (int i = 0; i < data.length; i++) {
            //       if(data[i][0]==1){ //If promotion
            DenseInstance inst = new DenseInstance(1, data[i]);
            inst.setDataset(dataset);
            dataset.add(inst);
            //     }
        }//*/

        return dataset;
    }

    public Instances getEmptyInstances() {
        return instances.stringFreeStructure();
    }

    public Instance getInstance(double[] instance, Instances dataset) {
        DenseInstance inst = new DenseInstance(1, instance);
        inst.setDataset(instances);
        return inst;
    }

    public WekaArffTableModel(ModelInjection mi, Instances instances, String[] classValues, Options options) {
        this.modelInjection = mi;
        this.instances = instances;

        arlNumericTypes.add("real");
        arlNumericTypes.add("integer");
        arlNumericTypes.add("numeric");
        nrOfClasses = classValues.length;
        //Init attributes
        //parseArffFile(readArffFile(fileName));
        Instance first = instances.firstInstance();
        for (int a = 0; a < first.numAttributes(); a++) {
            Attribute att = first.attribute(a);
            targetColumnIndex = first.classIndex();

            ArrayList<String> allowedValues = new ArrayList<String>();
            if (att.isNumeric()) {
                allowedValues.add("numeric");
            } else {
                Enumeration vals = att.enumerateValues();
                while (vals.hasMoreElements()) {
                    // allowedValues.add(Utils.quote((String) vals.nextElement()));
                    allowedValues.add((String) vals.nextElement());
                }
                //allowedValues.add(Utils.quote((String) vals.nextElement()));
            }
            this.addColumn(att.name(), allowedValues);

        }
        ArrayList<String> allowedValues = arlAllowedValues.get(targetColumnIndex);
        probOrder = new int[classValues.length];
        nrOfProbColumns = classValues.length;
        for (int g = 0; g < classValues.length; g++) {
            for (int c = 0; c < classValues.length; c++) {
                if (allowedValues.get(g).equalsIgnoreCase(classValues[c])) {
                    probOrder[g] = c;
                }
            }
        }
        options.setProbOrder(probOrder);

        //Read Data
        for (int row = 0; row < instances.numInstances(); row++) {
            for (int col = 0; col < instances.numAttributes(); col++) {
                if (instances.instance(row).attribute(col).isNumeric()) {
                    this.setValueAt(instances.instance(row).value(col), row, col);
                } else {
                    this.setValueAt(instances.instance(row).stringValue(col), row, col);
                }
            }
        }
        super.removeMissingValues();
        initColumnTypes();
        foldColumnIndex = -1;
        initFoldColumn(1);
        // initFold("F0");
    }

    public GP createJ48GP(Float confFactor, Environment env,String fold) {
        instances = getTrainInstances(fold);
        Classifier tree;
        try {
            tree = modelInjection.buildModel(instances, confFactor);
            return modelInjection.parseModel(tree, env);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public GP createJ48GP(Instances instances, Float confFactor, Environment env) {
        Classifier tree;
        try {
            tree = modelInjection.buildModel(instances, confFactor);
            return modelInjection.parseModel(tree, env);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    /*
     * public ArrayList<GP> createJ48GBootstrapTrees(Float confFactor,
     * Environment env){ Classifier tree; try { tree =
     * modelInjection.buildModel(instances, confFactor); return
     * modelInjection.parseModel(tree, env); } catch (Exception ex) {
     * ex.printStackTrace(); } return null;
    }
     */
    public WekaArffTableModel(ModelInjection mi, Instances instances) {
        super();
        this.modelInjection = mi;
        this.instances = instances;

        arlNumericTypes.add("real");
        arlNumericTypes.add("integer");
        arlNumericTypes.add("numeric");

        //Init attributes
        //parseArffFile(readArffFile(fileName));
        Instance first = instances.firstInstance();
        for (int a = 0; a < first.numAttributes(); a++) {
            Attribute att = first.attribute(a);
            targetColumnIndex = first.classIndex();

            ArrayList<String> allowedValues = new ArrayList<String>();
            if (att.isNumeric()) {
                allowedValues.add("numeric");
            } else {
                Enumeration vals = att.enumerateValues();
                while (vals.hasMoreElements()) {
                    String val = (String) vals.nextElement();
                    allowedValues.add(val);
                    //allowedValues.add(Utils.quote((String) vals.nextElement()));
                }
            }
            this.addColumn(att.name(), allowedValues);

        }

        //Read Data
        for (int row = 0; row < instances.numInstances(); row++) {
            for (int col = 0; col < instances.numAttributes(); col++) {
                if (instances.instance(row).attribute(col).isNumeric()) {
                    this.setValueAt(instances.instance(row).value(col), row, col);
                } else {
                    this.setValueAt(instances.instance(row).stringValue(col), row, col);
                }
            }
        }
        super.removeMissingValues();
        initColumnTypes();
        foldColumnIndex = -1;
        initFoldColumn(1);
        validate();

    }

    public double[] getDoubleArray(Instance ins) {
        double[] arr = new double[ins.numAttributes()];
        for (int a = 0; a < ins.numAttributes(); a++) {
            Attribute att = ins.attribute(a);
            if (att.isNumeric()) {
                arr[a] = ins.value(att);
            } else {
                arr[a] = getArlAllowedValues().get(a).indexOf(ins.stringValue(att));
            }
        }
        return arr;
    }

    /*
     * public void createInjectionModels(Environment environment) { Classifier
     * model = null; ArrayList<Integer> all = new ArrayList<Integer>(); for(int
     * i = 0; i < instances.numInstances();i++) all.add(i); ArrayList<Integer>
     * bag = new ArrayList<Integer>(); try { System.out.println("Reasonable
     * acc:" + calcReasonableAcc(10, 0.25f)); model =
     * getModelInjection().buildModel(instances, all, 0.25f);
     * System.out.println("Model trainAcc:" + calcAcc(model, all));
     * getModelInjection().parseModel(model, environment); } catch (Exception
     * ex) { ex.printStackTrace(); }
     *
     *
     * }
     */
    private void createBootstrap(ArrayList<Integer> outBoot, ArrayList<Integer> outBag) {
        int nrOfInstance = instances.numInstances();

        for (int i = 0; i < nrOfInstance; i++) {
            outBag.add(i);
        }

        while (outBoot.size() < nrOfInstance) {
            Integer i = (int) Math.floor(nrOfInstance * Options.rnd.nextDouble());
            outBoot.add(i);
            outBag.remove(i);
        }
    }

    public double calcReasonableAcc(int nrOfTrees, float confFactor, String fold) throws Exception {
        double trainAcc = 0;
        double testAcc = 0;
        instances = getTrainInstances(fold);
        Classifier model = null;
        ArrayList<Integer> boot = new ArrayList<Integer>();
        ArrayList<Integer> bag = new ArrayList<Integer>();
        for (int i = 0; i < nrOfTrees; i++) {
            boot.clear();
            bag.clear();
            createBootstrap(boot, bag);
            model = getModelInjection().buildModel(instances, boot, confFactor);
            trainAcc += calcAcc(model, boot) / nrOfTrees;
            testAcc += calcAcc(model, bag) / nrOfTrees;
        }
        return (trainAcc*2 + testAcc) / 3;
    }


    public double calcAcc(Classifier model, ArrayList<Integer> indices) throws Exception {

        double acc = 0;

        for (Integer i : indices) {
            if (model.classifyInstance(instances.instance(i)) == instances.instance(i).classValue()) {
                acc += 1.0 / indices.size();
            }
        }
        return acc;
    }

    /**
     * @return the modelInjection
     */
    public ModelInjection getModelInjection() {
        return modelInjection;
    }
}
